HTML5 × CSS3 × jQueryを真面目に勉強 – #19 JS の Math 関数を最適化出来ないか検証してみた
前回、前々回と画像処理について学びました。ピクセル単位での解析をするなど、何かにつけてループ処理を書くことが多くなりがちです。100 回程度のループならまだしも、画像処理となると 1000 回や 2000 回は当たり前、1万回以上ループすることも珍しくありません。そうなるとちょっとした処理の違いが大きなスピードの差を生むことになるわけです。塵も積もれば何とやらです。
で、今回本格的に画像処理をやってみて気づいたのが、Math 関数を結構な頻度で使うんだなぁ、ということでした。Flash(ActionScript) の世界では、ド派手なヴィジュアルでも軽快に動作させるためにループ処理の中では Math 関数の使用を避けるのがセオリーとなっています。では JavaScript の場合はどうなのかいくつかベンチマークをとってみました。
はじめに - Math 関数について
Math を直訳すると数学です。要するに数学的な定数と関数を提供するプロパティとメソッドを持った、JavaScript 組み込みのオブジェクトです。そのためコンストラクタを用いたりメソッドを呼ぶといったことをしなくとも、いきなりアクセスすることが出来ます。
Math オブジェクトには以下の様なプロパティとメソッドが定義されています。
プロパティ
E |
ネイピア数(オイラー数) - これは自然対数の底として用いられる数学定数で、約 2.718 |
---|---|
LN2 |
2 の自然対数 - 約 0.693 |
LN10 |
10 の自然対数 - 約 2.302 |
LOG2E |
2 を底とした E の対数 - 約 1.442 |
LOG10E |
10 を底とした E の対数 - 約 0.434 |
PI |
円周率 - 約 3.14159 |
SQRT1_2 |
1/2 の平方根 - つまり、1 割る 2 の平方根。約 0.707 |
SQRT2 |
2 の平方根 - 約 1.414 |
メソッド
abs |
引数として与えた数の絶対値を返す |
---|---|
acos |
引数として与えた数のアークコサインをラジアン単位で返す |
asin |
引数として与えた数のアークサインをラジアン単位で返す |
atan |
引数として与えた数のアークタンジェントをラジアン単位で返す |
atan2 |
引数の比率でのアークタンジェントを返す |
ceil |
引数として与えた数以上の最小の整数を返す |
cos |
引数として与えた数のコサインを返す |
exp |
Enumberを返す。ここでの number は引数で、E は自然対数の底である、ネイピア数(オイラー数)となる。 |
floor |
引数として与えた数以下の最大の整数を返す |
imul |
2 つの引数をとり、C 言語の様な 32 ビット乗算の結果を返す |
log |
引数として与えた数の自然対数(底は E)を返す |
max |
引数として与えた複数の数の中で最大の数を返す |
min |
引数として与えた複数の数の中で最小の数を返す |
pow |
第一引数の値を第二引数の値で累乗した値を返す |
random |
0 以上 1 未満の疑似乱数を返す |
round |
引数として与えた数を四捨五入して、最も近似の整数を返す |
sin |
引数として与えた数のサインを返す |
sqrt |
引数として与えた数の平方根を返す |
tan |
引数として与えた数のタンジェントを返す |
Math 関数と最適化したコードのベンチマークをとってみる
いくつかの Math 関数を最適化したコードで書き直すとどれくらい速度に差が生まれるのか、実際にベンチマークをとって検証してみたいと思います。この辺りのノウハウは、JavaScript 関連よりも Flash 関連で探したほうが有益な情報が多く見つかったりするので、それらを参考にしています。言語は違いますがどちらも ECMAScript 準拠なので、大概はそのままコピペで流用することが出来るんですよね。
レガシーなブラウザによっては予期せぬ動作をする可能性があるので、その辺りのチェックは忘れずに。
Math.floor();
小数点以下の数値を切り捨てる時は Math.floor(); を使いますが、以下のような論理和を用いたコードで同様の処理が実現できます。
数値 | 0;
他にもビット演算を使うことで実現することも出来ます。
数値 >> 0;
数値を 0 ビット右にシフトするというものです。理論上はビット演算の方が速いということになります。
ベンチマーク
即席ですが、ベンチマーク測定用のコードを組みました。
測定内容
- Math.floor();とそれに代わる処理をそれぞれ1000万回呼びだし、所要時間を測定
こちらより実際に測定することが出来ます。
いかがでしたでしょうか。ちなみに僕の環境で測定した結果は以下の通り。
PC 環境
- OS
- OS X 10.9 Mavericks
- CPU
- 2.4 GHz Intel Core i7
- RAM
- 8 GB 1600 MHz DDR3
- Browser
- Chrome 30.0
- Firefox 24
- Safari 7.0
- Chrome Canary 32
- WebKit Nightly Builds
- InternetExplorer 10 *1
iPhone 環境
- 機種
- iPhone 5
- Browser
- Safari
- Chrome
PC 環境
iPhone 環境
PC 版 Chrome のみ Math 関数の方が速いという結果になりました。正しく言うならば、Chrome に搭載されている V8 JavaScript Engine の測定結果ということになります。この現行版 Chromeよりも 2 バージョン先の ChromeCanary では他のブラウザ同様、ビット演算と論理和のほうが速いというのもなかなか興味深いです。
Math.round();
整数の四捨五入をするための関数です。上記の小数点以下切り捨てのテクニックを応用することで、こちらも Math 関数を使わずに実現することができます。
(数値 + 0.5) | 0;
同様の方法でビット演算でも実現できます。
(数値 + 0.5) >> 0;
こちらより実際に測定することが出来ます。
僕の環境で測定した結果は以下の通り。
PC 環境
iPhone 環境
Math.floor(); と似たような結果ですが、Math.floor();と違ってChromeの結果が逆転しています。何度も測定してみましたが、ビット演算と論理和のほうが速いという結果は変わりませんでした。
Math.max();, Math.min();
2つの数値から最大値もしくは最小値を求める関数として Math.max(); と Math.min(); があります。やや冗長な書き方になりますが、条件演算子の : ? を利用することで同様のことが実現できます。
// MAth.max(a, b); (a > b) ? a : b;
// Math.min(a, b); (a < b) ? a : b; [/javascript]
こちらより実際に測定することが出来ます。
僕の環境で測定した結果は以下の通り。
PC 環境
iPhone 環境
今度は Firefox において Math 関数と条件演算子の結果がほかブラウザと逆になっています。
Math.abs()
数値の絶対値を求めるための関数です。Math.max()、Math.min() 同様、やや冗長なので場合によっては可読性が大きく損なわれることになりますが、結構なパフォーマンス改善が期待出来ます。
(数値 < 0) ? -数値 : 数値; [/javascript]
こちらより実際に測定することが出来ます。
僕の環境で測定した結果は以下の通り。
PC 環境
iPhone 環境
おわりに
こうしていざ実際に試してみると、Flash と違ってブラウザや Math 関数によって結果が異なるというのが意外でした。今回試した方法は関数の呼び出しをせずに式をステートメントとして直接書くことで高速化を実現するというものでした。一度きりの呼び出し箇所ならば大したチューニングにはならないので、可読性を優先させてMath関数を呼び出してしまったほうがメリットが大きいですが、塵も積もれば山となるというやつで、回数が多く呼ばれるようなループ処理の中といった際には、こういったチューニングを実践してみるのが良いかと思います。もちろん動作保証するブラウザを勘定にいれた上で実装するかどうかを判断するべきですが。
参考サイト
高速化
- FN1109002 - 数値の切捨てや四捨五入の最適化 - Flash : テクニカルノート
- ActionScript 3.0 パフォーマンスチューニング - 速い、軽い、うまいスクリプティングを目指す - JaGra PROFESSIONAL SCHOOL Seminar
脚注
- VMWare Fusion で仮想マシンを立ち上げ、そこでWindows環境を動かしています。 ↩